#include <windows.h>
#include <windowsx.h>

#include <stdio.h>
#include <fcntl.h>
#include <io.h>

#include "lua_c_api/include/lua.hpp"

#include "reaper_plugin/reaper_plugin.h"
#include "reaper_plugin/reaper_plugin_functions.h"

#include "glue.h"
#include "GlueTypes.h"


HWND hReaLuaWnd;

#define ID_EDITOUT	(1)
#define ID_EDITIN	(2)
#define ID_PROMPT	(3)

HWND hwndOutput; 
HWND hwndInput; 
HWND hwndPrompt;

// maximum mumber of lines the output console should have

static const WORD MAX_CONSOLE_LINES = 500;

static int fcgets(lua_State *L);
static int luaB_printrl (lua_State *L);
static int fdump(lua_State *L);
static int frestart(lua_State *L);

static int fexit(lua_State *L)
{
	if(hReaLuaWnd)
	{
		DestroyWindow(hReaLuaWnd);
	}
	return 0;
}

static int fcls(lua_State *L)
{
	Edit_SetText(hwndOutput, "");
	return 0;
}

/*
	The following are in the _G global space because
	the normal print() is, so its mate input() goes
	in global space, and since exit() and cls() also
	"talk to" the ReaLua Window, they go there too.
*/

int reglobals(lua_State *L)
{
	int errar = luaL_dostring(L, "oldprint = print");
	lua_register(L, "print", luaB_printrl);
	lua_register(L, "input", fcgets);
	lua_register(L, "exit", fexit);
	lua_register(L, "cls", fcls);
	lua_register(L, "dump", fdump);
//	lua_register(L, "restart", frestart);
	return errar;
}

// adapted from lua.c to use our {reaper.realua} information table:

static int realuainit(lua_State *L)
{
	lua_getglobal(L, "reaper");			// {reaper}
	lua_pushstring(L, "realua");		// {reaper} "realua"
    lua_gettable(L, -2);				// {reaper} {reaper.realua}
	lua_pushstring(L, "REALUA_INIT");	// {reaper} {reaper.realua} "REALUA_INIT" (key)
	lua_gettable(L, -2);				// {reaper} {reaper.realua} REALUA_INIT	  (value)
	const char *pinit = lua_tostring(L, -1);	// does not mess with stack
	lua_pop(L, 3);								// clean up stack

	if (pinit == NULL) return 0;  /* status OK */
	else if (pinit[0] == '@')
		return luaL_dofile(L, pinit+1);

	return luaL_dostring(L, pinit);
}

lua_State *Ls;

void startLua(void)
{
	if(!Ls)
	{
		Ls = lua_open();   /* opens Lua */
		if(Ls)
		{
			luaL_openlibs(Ls);
			reaperMainTable(Ls);
			reglobals(Ls);
			realuainit(Ls);
		}
	}
}

void stopLua(void)
{
	if(Ls)
	{
		lua_close(Ls);
		Ls = NULL;
	}
}

// okay so this doesn't work, there may be more to tear down
// and set back up!  So don't call this yet (ever?).

static int frestart(lua_State *L)
{
	stopLua();
	startLua();
	return 0;
}

// adapted from lua.c:

static int incomplete (lua_State *L, int status) {
	if (status == LUA_YIELD) {		// was:		LUA_ERRSYNTAX) {
    size_t lmsg;
    const char *msg = lua_tolstring(L, -1, &lmsg);
    const char *tp = msg + lmsg - (sizeof(LUA_QL("<eof>")) - 1);
    if (strstr(msg, LUA_QL("<eof>")) == tp) {
      lua_pop(L, 1);
      return 1;
    }
  }
  return 0;  /* else... */
}

void getprompt(lua_State *L, bool firstline, LPSTR lpszPlace)
{
	size_t sizePlace = HeapSize( GetProcessHeap(), 0, lpszPlace );

	if(firstline)
	{
		strcpy_s(lpszPlace, sizePlace, ">");	// set to default firstline promp
		lua_getglobal(L, "_PROMPT");
	}
	else
	{
		strcpy_s(lpszPlace, sizePlace, ">>");	// set to default continuation promp
		lua_getglobal(L, "_PROMPT2");
	}

	if(!lua_isnil(L, -1))
	{
		const char *pg = luaL_checkstring(L, -1);
		strcpy_s(lpszPlace, sizePlace, pg);
	}

	lua_pop(L, 1);	// pop the global string or the nil
}

void TrimEditWindow(void)
{
	if(hwndOutput)
	{
		LRESULT linec = Edit_GetLineCount( hwndOutput );
		if(linec > (MAX_CONSOLE_LINES + 100) )
		{
			for(int i = MAX_CONSOLE_LINES; i < linec; i+= 100)
			{
				LRESULT lidex = Edit_LineIndex( hwndOutput, 100);
				if(lidex > 0)
				{
					Edit_SetSel( hwndOutput, 0, lidex);
					Edit_ReplaceSel( hwndOutput, "");
				}
			}
		}
	}
}

// sends a c-string to the end of the output child window:

void sendcs(char *cs)
{
	if( hwndOutput && cs )
	{
//		TrimEditWindow();
		Edit_SetSel( hwndOutput, 0xffff, 0xffff);	// idiom to scoot caret to end
		Edit_ReplaceSel( hwndOutput, cs );
		TrimEditWindow();
	}
}

LPSTR	lpszSavedText;

void CloseReaLuaWindow(void)
{
	if(lpszSavedText)	// recycle it
	{
		HeapFree( GetProcessHeap(), 0, lpszSavedText );
		lpszSavedText = 0;
	}

	LRESULT	saveSize = Edit_GetTextLength( hwndOutput ) + 1;
	lpszSavedText = (LPSTR) HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, saveSize );
	Edit_GetText( hwndOutput, lpszSavedText, saveSize );

	HWND hw = GetMainHwnd();
	BringWindowToTop(hw);

	hReaLuaWnd = 0;
}

static int fdump(lua_State *L)
{
	if(hReaLuaWnd == 0)
	{
		return 0;
	}

	const char *fn="dump.txt";
	if(lua_gettop(L) >= 1)
	{
		fn = luaL_checkstring(L, 1);
	}
	bool append = false;			// not appending
	DWORD cremode = CREATE_ALWAYS;	// wipe out existing, if any
	if(lua_gettop(L) >= 2)
	{
		append = luaL_checkboolean(L, 2);
		if(append)
		{
			cremode = OPEN_ALWAYS;
		}
	}

	HANDLE hFile = CreateFile(fn,                // name of the write
                       GENERIC_WRITE,          // open for writing
                       0,                      // do not share
                       NULL,                   // default security
                       cremode,					// cremotions in motion
                       FILE_ATTRIBUTE_NORMAL,  // normal file
                       NULL);                  // no attr. template

	if (hFile == INVALID_HANDLE_VALUE) 
	{ 
		luaL_error(L, "Could not open file error %d", GetLastError() );
		return 0;
	}

	if(append)
	{
		DWORD sfp = SetFilePointer(hFile, 0, NULL, FILE_END);
		if(sfp == INVALID_SET_FILE_POINTER)
		{
            CloseHandle(hFile);
			luaL_error(L, "Could not move file pointer");
			return 0;
		}
	}

	LRESULT	saveSize = Edit_GetTextLength( hwndOutput ) + 1;
	LPSTR lipschitz = (LPSTR) HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, saveSize );
	Edit_GetText( hwndOutput, lipschitz, saveSize);

    DWORD dwBytesWritten = 0;
	DWORD dwBytesToWrite = saveSize;
	while (dwBytesWritten < dwBytesToWrite)
    {
        if( FALSE == WriteFile(hFile,           // open file handle
                               lipschitz + dwBytesWritten,     // start of data to write
                               dwBytesToWrite - dwBytesWritten, // number of bytes to write
                               &dwBytesWritten, // number of bytes that were written
                               NULL)            // no overlapped structure
          )
        {
			HeapFree( GetProcessHeap(), 0, lipschitz );
			CloseHandle(hFile);
			luaL_error(L, "Could not write file error %d", GetLastError() );
            return 0;
        }
    }

	CloseHandle(hFile);
	HeapFree( GetProcessHeap(), 0, lipschitz );
	lua_pushnumber(L, dwBytesWritten);
	return 1;
}

gaccel_register_t gacreg=
{
	{FALT|FVIRTKEY,'0',0},
	"Open Lua Shell"
};

// Global Variables:
HINSTANCE hInst;									// current instance
TCHAR szWindowClass[] = "ReaLuaWindow";	// the main window class name

// forwards:
LRESULT CALLBACK	WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK	InputProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK	OutputProc(HWND, UINT, WPARAM, LPARAM);
void				PrintStdOut(void);

ATOM MyRegisterClass(HINSTANCE hInstance)
{
	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);

	wcex.style			= CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc	= WndProc;
	wcex.cbClsExtra		= 0;
	wcex.cbWndExtra		= 0;
	wcex.hInstance		= hInstance;
	wcex.hIcon			= NULL; //LoadIcon(hInstance, MAKEINTRESOURCE(IDI_SILLYWINDOW));
	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW+1);
	wcex.lpszMenuName	= NULL; //MAKEINTRESOURCE(IDC_SILLYWINDOW);
	wcex.lpszClassName	= szWindowClass;
	wcex.hIconSm		= NULL; //LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

	ATOM regatom = RegisterClassEx(&wcex);

	if(!regatom)
	{
		DWORD er = GetLastError();
		printf("", er);
	}

	return regatom;
}

BOOL CreateReaLuaWindow(void)
{
	TCHAR szTitle[] = "ReaLua";			// The title bar text

	hReaLuaWnd = CreateWindow(szWindowClass, szTitle,
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
		NULL,
		NULL, hInst, NULL);

	if (!hReaLuaWnd)
	{
		DWORD er = GetLastError();
		printf("",er);
		return FALSE;
	}

	if(lpszSavedText)
	{
		sendcs( lpszSavedText );
		if( hwndOutput )
		{
			Edit_Scroll(hwndOutput, MAX_CONSOLE_LINES * 2, 0);	// comfortably far down
		}
	}

	return TRUE;
}

// just to be complete about it:
#define Edit_SetLeftMargin(hw,amt)  SendMessage( (hw), EM_SETMARGINS, EC_LEFTMARGIN,  (amt) );
#define Edit_SetRightMargin(hw,amt) SendMessage( (hw), EM_SETMARGINS, EC_RIGHTMARGIN, (amt) );

WNDPROC oldInputProc, oldOutputProc;

void CreateEditWindows(HWND hWnd)
{
	HFONT	hfontIO;
	HFONT   hfontButton;

	hwndOutput = CreateWindow(TEXT ("edit"),      // predefined class 
		NULL,        // no window title 
		WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL |
		ES_LEFT | ES_MULTILINE |
		ES_AUTOVSCROLL | ES_AUTOHSCROLL |
		ES_READONLY, 
		0, 0, 0, 0,  // set size in WM_SIZE message 
		hWnd,        // parent window 
		(HMENU) ID_EDITOUT,   // edit control ID 
		(HINSTANCE) GetWindowLong(hWnd, GWL_HINSTANCE), 
		NULL);       // pointer not needed 

		// give the Output Child Window a new window procedure ("subclass" the window)
	oldOutputProc = (WNDPROC) GetWindowLongPtr( hwndOutput, GWL_WNDPROC);
	SetWindowLongPtr( hwndOutput, GWL_WNDPROC, (LONG) OutputProc);

	// now the input window
	hwndInput = CreateWindow(TEXT ("edit"),      // predefined class 
		NULL,        // no window title 
		WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL |
		ES_LEFT | 
		ES_WANTRETURN | ES_MULTILINE | ES_AUTOVSCROLL |
		ES_AUTOHSCROLL, 
		0, 0, 0, 0,  // set size in WM_SIZE message 
		hWnd,        // parent window 
		(HMENU) ID_EDITIN,   // edit control ID 
		(HINSTANCE) GetWindowLong(hWnd, GWL_HINSTANCE), 
		NULL);       // pointer not needed 

	// give the Input Child Window a new window procedure ("subclass" the window)
	oldInputProc = (WNDPROC) GetWindowLongPtr( hwndInput, GWL_WNDPROC);
	SetWindowLongPtr( hwndInput, GWL_WNDPROC, (LONG) InputProc);

	// give it the keyboard focus
	SetFocus( hwndInput );

	// and here is our nice little prompt button
	LPSTR theprompt = (LPSTR) HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, BUFSIZE);
	getprompt(Ls, true, theprompt);

	hwndPrompt = CreateWindow(TEXT ("static"),      // predefined class 
		theprompt,					// window title 
		WS_CHILD | WS_VISIBLE 
		| SS_CENTER,			// flags 
		0, 0, 0, 0,				// set size in WM_SIZE message 
		hWnd,					// parent window 
		(HMENU) ID_PROMPT,		// control ID 
		(HINSTANCE) GetWindowLong(hWnd, GWL_HINSTANCE), 
		NULL);					 // pointer not needed 

	HeapFree( GetProcessHeap(), 0, theprompt);

// now set the fontses

	hfontIO = CreateFont(
		16, 0, 0, 0,
		FW_REGULAR, 0, 0, 0, ANSI_CHARSET,
		OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
		CLEARTYPE_QUALITY, // ANTIALIASED_QUALITY
		DEFAULT_PITCH | FF_DONTCARE,
		TEXT("Lucida Console"));

	if(hfontIO)
	{
		SetWindowFont( hwndInput, hfontIO, NULL);
		SetWindowFont( hwndOutput, hfontIO, NULL);
	}

	hfontButton = CreateFont(
		36, 0, 0, 0,
		FW_REGULAR, 0, 0, 0, ANSI_CHARSET,
		OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
		ANTIALIASED_QUALITY,
		DEFAULT_PITCH | FF_DONTCARE,
		TEXT("Lucida Console"));

	if(hfontButton)
	{
		SetWindowFont( hwndPrompt, hfontButton, NULL);
	}

	// set the margins and tab stops on the IO windows
	UINT stops = 4 * 4;	// 4 spaces, a space seems to be about 4 Template Units wide

	Edit_SetLeftMargin( hwndInput, 4 );
	Edit_SetTabStops( hwndInput, 1, &stops );

	Edit_SetLeftMargin( hwndOutput, 4 );
	Edit_SetTabStops( hwndOutput, 1, &stops );
}

void SizeEditWindows(LPARAM lParam)
{
	int width =  LOWORD(lParam);        // width of client area 
	int height = HIWORD(lParam);	    // height of client area 

#define	pwidth  44
#define pheight 80
            // Make the edit control the size of the window's client area. 

	MoveWindow(hwndOutput, 
			   0, 0,                  // starting x- and y-coordinates 
			   width,					// width of client area 
			   height - pheight,        // height of client area 
			   TRUE);                 // repaint window 
	MoveWindow(hwndInput, 
			   pwidth,				//  start x
			   height - pheight,		// start y-coordinate 
			   width - pwidth,		// width of client area 
			   pheight,				// height of client area 
			   TRUE);				// repaint window 
	MoveWindow(hwndPrompt, 
			   0,					//  start x
			   height - pheight,		// start y-coordinate 
			   pwidth,				// width of client area 
			   pheight,				// height of client area 
			   TRUE);				// repaint window 
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	PAINTSTRUCT ps;
	HDC hdc;

	switch (message)
	{
		case WM_ACTIVATE:
			switch( LOWORD(wParam) )
			{
			case WA_ACTIVE:
			case WA_CLICKACTIVE:
				if( hwndInput )
				{
					SetFocus( hwndInput );
					return 0;
				}
			break;
			}
		break;

		case WM_CREATE:
			CreateEditWindows(hWnd);
			return 0;
		break;

		case WM_DESTROY:
			CloseReaLuaWindow();
			return 0;
		break;

		case WM_COMMAND:
		break;

		case WM_PAINT:
			hdc = BeginPaint(hWnd, &ps);
			// TODO: Add any drawing code here...
			EndPaint(hWnd, &ps);
			return 0;
		break;

		case WM_SIZE: 
			SizeEditWindows(lParam);
			return 0;
		break;	

		case WM_CTLCOLORSTATIC:
			if (IsWindow((HWND)lParam))  
			{  
				if ( hwndOutput == (HWND) lParam )  
				{  
					SetTextColor((HDC)wParam, RGB(0,0,0) );  
					return (LRESULT) GetStockObject(WHITE_BRUSH);  
				}
			}  
		break;

		default:
		break;
	}
	return DefWindowProc(hWnd, message, wParam, lParam);
}

char	indentChars[] = " \t";

int getIndent(HWND hWnd, LPSTR lpszBuffer, int *lidex)
{
	int indent = 0;
	*lidex = 0;

	LRESULT linec = Edit_GetLineCount( hWnd );
	if(linec > 0)
	{
		*lidex = Edit_LineIndex( hWnd, linec - 1 );
		indent = strspn(lpszBuffer + *lidex, indentChars);//" ");
	}

	return indent;
}

// "Control-Enter Data" stuff the ctrl-enter handler needs
// to keep between calls:

typedef struct
{
	LPSTR	szAccum;	// a bunch of lines catted
	LPSTR	szBuffer;	// a single line, kept between calls why?  just to keep from realloc each time?
	LPSTR	szScratch;	// whaddyathink?
} CEData, *lpCEData;

lpCEData lpcedata;

void freeCEData(void)
{
	if(lpcedata)
	{
		if(lpcedata->szBuffer)
			HeapFree( GetProcessHeap(), 0, lpcedata->szBuffer);

		if(lpcedata->szAccum)
			HeapFree( GetProcessHeap(), 0, lpcedata->szAccum);

		if(lpcedata->szScratch)
			HeapFree( GetProcessHeap(), 0, lpcedata->szScratch);

		HeapFree( GetProcessHeap(), 0, lpcedata);
	}
}

void allocCEData(void)
{
	if(!lpcedata)
	{
		lpcedata = (lpCEData)
			HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(CEData) );

		if(lpcedata)
		{
			lpcedata->szBuffer = (LPSTR)
				HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, BIGBUF );

			lpcedata->szAccum = (LPSTR)
				HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, BIGBUF );

			lpcedata->szScratch = (LPSTR)
				HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, BIGBUF );

			if( !( lpcedata->szBuffer && lpcedata->szAccum && lpcedata->szScratch ) )
				freeCEData();
		}
	}
}

void HandleCtrlEnter(HWND hWnd)
{
	int error = 0;
	int inco = 0;

	allocCEData();	// we want a better place for this, one that makes sense for its free() func too

	// Get and echo the new text
	Edit_GetText(hWnd, lpcedata->szBuffer, BIGBUF);
	sendcs(lpcedata->szBuffer);
	sendcs("\r\n");

	// Check it for special equal-sign print syntax
	LRESULT linec = Edit_GetLineCount( hWnd );	// only on first line
	if(linec == 1)
	{
		if(lpcedata->szBuffer[0] == '=')
		{
			sprintf_s(lpcedata->szScratch, BIGBUF, "print( %s );", lpcedata->szBuffer + 1);
			strcpy_s(lpcedata->szBuffer, BIGBUF, lpcedata->szScratch);
		}
	}

	// calculate its indentation
	int lidex;
	int indent = getIndent( hWnd, lpcedata->szBuffer, &lidex );

	// try to put new text into accumulator (after a space spacer)
	strcat_s(lpcedata->szAccum, BIGBUF, " ");
	strcat_s(lpcedata->szAccum, BIGBUF, lpcedata->szBuffer);

	// then see what Lua thinks of it
	error = luaL_dostring(Ls, lpcedata->szAccum);
	inco = incomplete(Ls, error);

	// if incomplete, set the prompt for continuation
	LPSTR theprompt = (LPSTR) HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, BUFSIZE);

	if(inco)
	{
		getprompt(Ls, false, theprompt);
		Static_SetText(hwndPrompt, theprompt);
	}
	else	//	if(!incomplete(Ls, error))
	{
		if(error)
		{
			const char *es = lua_tostring(Ls, -1);
			fprintf(stdout, "\n%s\n", es);
		}

		lpcedata->szAccum[0] = 0;	// clear chunk
		indent = 0;
		getprompt(Ls, true, theprompt);
		Static_SetText(hwndPrompt, theprompt);
	}
	HeapFree( GetProcessHeap(), 0, theprompt);

	// clear input window by preloading with the indent chars
	lpcedata->szBuffer[lidex + indent] = 0;	// set end-of-string past indent chars (if any)
	Edit_SetText(hWnd, lpcedata->szBuffer + lidex);
	Edit_SetSel( hWnd, 0xFFFF, 0xFFFF);
}

void HandleEnter(HWND hWnd)
{
	int lidex;
	int indent = 0;

	LRESULT	textSize = Edit_GetTextLength( hWnd ) + 1;
	LPSTR lpszText = (LPSTR) HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, textSize );
	Edit_GetText( hWnd, lpszText, textSize);

	lidex = Edit_LineIndex(  hWnd, -1 );	// index of current line
	indent = strspn(lpszText + lidex, indentChars);
	lpszText[lidex + indent] = 0;	// set end-of-string past indent chars (if any)

	Edit_ReplaceSel( hWnd, "\r\n" );
	Edit_ReplaceSel( hWnd, lpszText + lidex );

	HeapFree( GetProcessHeap(), 0, lpszText );
}

// more complete version of Edit_GetSel():

#define Edit_GetSelect(hw,wp)	SendMessage((hw), EM_GETSEL, (WPARAM) (wp), NULL);

LRESULT CALLBACK InputProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	int	start;

	switch(message)
	{
		case WM_KEYDOWN:
			switch( wParam )
			{
				case VK_RETURN:
					if( GetKeyState(VK_CONTROL) & 0x8000 )
					{
						HandleCtrlEnter(hWnd);
						PrintStdOut();			// in case someone used stdio by accident ;-)
					}
					else
					{
						HandleEnter(hWnd);
					}

					return 0;	// we eat the VK_RETURN event
				break;

				case 'A':
					if( GetKeyState(VK_CONTROL) & 0x8000 )
					{
						Edit_SetSel( hWnd, 0, -1 );
						return 0;
					}
				break;
			}

		break;

// for some reason a CR/LF is in the buffer
// so we take this opportunity to snip it

		case WM_KEYUP:
			if( wParam == VK_RETURN )
			{
				Edit_GetSelect( hWnd, &start);
				Edit_SetSel( hWnd, start - 2, start );
				Edit_ReplaceSel( hWnd, "");

				return 0;	// eat it
			}
		break;
	}

	return CallWindowProc( oldInputProc, hWnd, message, wParam, lParam);
}

LRESULT CALLBACK OutputProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
		case WM_KEYDOWN:
			switch( wParam )
			{
				case VK_TAB:
					if( hwndInput )
					{
						SetFocus( hwndInput );
						return 0;
					}
				break;

				case 'A':
					if( GetKeyState(VK_CONTROL) & 0x8000 )
					{
						Edit_SetSel( hWnd, 0, -1 );
						return 0;
					}
				break;
			}

		break;
	}

	return CallWindowProc( oldOutputProc, hWnd, message, wParam, lParam);
}

// converts a Lua (Unix) LFs to DOS ('Doze) CR/LFs
// leaves it in the Ctrl-ENTER Data's scratchpad, might want to think on this

char *us2ds(char *str)
{
	if(!lpcedata)
		return NULL;

	char *src = str;
	char *dst = lpcedata->szScratch;
	int pos = 0;

	while(*src)
	{
		if(*src == 0xA && pos > 0)
			if(src[-1] != 0xD)
				*dst++ = 0xD;

		*dst++ = *src++;
		pos++;
	}
	*dst = 0;

	return lpcedata->szScratch;		// this stuff is no longer just "Control Enter Data"
}

// adapted from http://www.lua.org/source/5.1/lbaselib.c.html#luaB_print

static int luaB_printrl (lua_State *L) {
  int n = lua_gettop(L);  /* number of arguments */
  int i;
  lua_getglobal(L, "tostring");
  for (i=1; i<=n; i++) {
    const char *s;
    lua_pushvalue(L, -1);  /* function to be called */
    lua_pushvalue(L, i);   /* value to print */
    lua_call(L, 1, 1);
    s = lua_tostring(L, -1);  /* get result */
    if (s == NULL)
      return luaL_error(L, LUA_QL("tostring") " must return a string to "
                           LUA_QL("print"));
    if (i>1) sendcs("\t");
    sendcs( us2ds( (char *)s ) );
    lua_pop(L, 1);  /* pop result */
  }
  sendcs("\r\n");
  return 0;
}

#define ID_TEXT		200
#define ID_ANSWER	201

typedef struct 
{
	LPSTR	szPrompt;
	LPSTR	szDefault;
	LPSTR	szAnswer;
} ioStrs, *LPioStrs;

void freeIoStrs(LPioStrs lpstrs)
{
	if(lpstrs)
	{
		if(lpstrs->szPrompt)
			HeapFree( GetProcessHeap(), NULL, lpstrs->szPrompt );

		if(lpstrs->szDefault)
			HeapFree( GetProcessHeap(), NULL, lpstrs->szDefault );

		if(lpstrs->szAnswer)
			HeapFree( GetProcessHeap(), NULL, lpstrs->szAnswer );

		HeapFree( GetProcessHeap(), NULL, lpstrs );
	}
}

LPioStrs allocIoStrs(void)
{
	LPioStrs lpstrs = (LPioStrs)
		HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(ioStrs) );
	if(lpstrs)
	{
		lpstrs->szPrompt = (LPSTR)
			HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, BIGBUF );

		lpstrs->szDefault = (LPSTR)
			HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, BIGBUF );

		lpstrs->szAnswer = (LPSTR)
			HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, BIGBUF );

		if( !( lpstrs->szPrompt && lpstrs->szDefault && lpstrs->szAnswer ) )
		{
			freeIoStrs(lpstrs);
			return NULL;
		}
	}

	return lpstrs;
}

#define Dlg_NextCtl(hw,wp)	SendMessage(hw, WM_NEXTDLGCTL, (WPARAM) wp, TRUE);

INT_PTR CALLBACK DialogProc( HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	HWND hwndOkay, hwndAnswer, hwndPrompt;
	RECT rcDlg;
	static LPioStrs	lpstrs;

	switch (message) 
	{ 
	case WM_INITDIALOG:
		lpstrs = (LPioStrs) lParam;

		GetWindowRect(hwndDlg, &rcDlg); 
		OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top); 
		rcDlg.bottom -= 32;

		hwndOkay = CreateWindow("BUTTON",
							"OK",
							WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON,
							(rcDlg.right/2) - 22, rcDlg.bottom - 32, 44, 24,
							hwndDlg,
							(HMENU) IDOK,
							hInst,
							NULL);

		hwndPrompt = CreateWindow("STATIC",
							lpstrs->szPrompt,
							WS_CHILD | WS_VISIBLE | SS_LEFT,	// uh read only ?
							10, 10, rcDlg.right - 20, rcDlg.bottom * 2 / 3,
							hwndDlg,
							(HMENU) ID_TEXT,
							hInst,
							NULL);

		hwndAnswer = CreateWindow("EDIT",
							lpstrs->szDefault,
							WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_LEFT,
							10, rcDlg.bottom - 60, rcDlg.right - 20, 24,
							hwndDlg,
							(HMENU) ID_ANSWER,
							hInst,
							NULL);

		Dlg_NextCtl(hwndDlg, hwndAnswer);
	break;

	case WM_COMMAND: 
		switch (LOWORD(wParam)) 
		{ 
		case IDOK: 
			if(lpstrs)
			{
				GetDlgItemText(	hwndDlg, ID_ANSWER, lpstrs->szAnswer, BIGBUF);
			}
			// Fall through. 

		case IDCANCEL: 
			EndDialog(hwndDlg, wParam); 
			return TRUE; 
		} 
	} 
	return FALSE; 
}

LRESULT DisplayMyMessage(HINSTANCE hinst, HWND hwndOwner, LPioStrs lpstrs,
						 LPRECT rp)
{
    LPDLGTEMPLATE lpdt;
    LPWORD lpw;
    LRESULT ret;

	LONG baselong = GetDialogBaseUnits();
	int baseunitX = LOWORD(baselong);
	int baseunitY = HIWORD(baselong);

	int dx = MulDiv(rp->left,	4, baseunitX);
	int dy = MulDiv(rp->top,	8, baseunitY);
	int dw = MulDiv(rp->right,	4, baseunitX);
	int dh = MulDiv(rp->bottom,	8, baseunitY);

	lpdt =	(LPDLGTEMPLATE) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 1024);	// comfortably big
    if (!lpdt)
        return -1;
 
    // Define a dialog box in memory.
 
    lpdt->style = WS_POPUP | WS_BORDER | WS_SYSMENU | DS_MODALFRAME | WS_CAPTION;
    lpdt->cdit = 0;         // Naked - makes children in INITDIALOG
    lpdt->x  = dx;  lpdt->y  = dy;
    lpdt->cx = dw;  lpdt->cy = dh;

    lpw = (LPWORD)(lpdt + 1);
    *lpw++ = 0;             // No menu
    *lpw++ = 0;             // Predefined dialog box class (by default)

    MultiByteToWideChar(CP_ACP, 0, "ReaLua Input ", -1, ((LPWSTR)lpw), 50);

	// now make our naked box which will adorn itself with I/O child windows

    ret = DialogBoxIndirectParam(  hinst, 
		                           (LPDLGTEMPLATE)lpdt, 
				                   hwndOwner, 
						           (DLGPROC)DialogProc,
								   (LPARAM) lpstrs
								   ); 
	if(ret == -1)
	{
		DWORD ler = GetLastError();
		fprintf(stdout, "Dialog Box Error:  %d", ler);
	}

    HeapFree(GetProcessHeap(), 0, lpdt); 
    return ret; 
}

static int fcgets(lua_State *L)
{
	char	*defprompt = "";
	char	*defdefalt = "";	// spelled to avoid conflict with "default" keyword
	char	*prompt = defprompt;
	char	*defalt = defdefalt;
	RECT	pp = { 20, 20, 300, 150 };

	int	top = lua_gettop(L);

	if(top >= 1)
	{
		prompt = (char *) lua_tostring(L, 1);
	}

	if(top >= 2)
	{
		defalt = (char *) lua_tostring(L, 2);
	}

	if(top >= 4)	// p,d, x,y,w,h --or-- p,d, w, h
	{
		if(top >= 6)	// p,d, x,y,w,h
		{
			pp.left		= luaL_checkinteger(L, 3);
			pp.top		= luaL_checkinteger(L, 4);
			pp.right	= luaL_checkinteger(L, 5);
			pp.bottom	= luaL_checkinteger(L, 6);
		}
		else			// p,d, w, h
		{
			pp.right	= luaL_checkinteger(L, 3);
			pp.bottom	= luaL_checkinteger(L, 4);
		}
	}

	LPioStrs lpstrs = allocIoStrs();
	if(!lpstrs)
	{
		return 0;
	}

	// set the input fields:
	strcat_s(lpstrs->szPrompt, BIGBUF, prompt);
	strcat_s(lpstrs->szDefault, BIGBUF, defalt);

	int ret = DisplayMyMessage(hInst, hwndOutput, lpstrs, &pp );

	// get the output field if any:
	if(lpstrs->szAnswer[0])	// if we have a string
	{
		lua_pushstring(L, lpstrs->szAnswer);
	}
	else
	{
		lua_pushnil(L);
	}

	freeIoStrs(lpstrs);

	return 1;
}

// http://stackoverflow.com/questions/7664/windows-c-how-can-i-redirect-stderr-for-calls-to-fprintf

HANDLE	hReadPipe;
HANDLE	hWritePipe;

void RedirectStdOut(void)
{
	if(!hReadPipe)
	{
		CreatePipe( &hReadPipe, &hWritePipe, NULL, 0 );
		stdout->_file = _open_osfhandle( (long)hWritePipe, _O_TEXT);
		setvbuf( stdout, NULL, _IONBF, 0 );
	}
}

void PrintStdOut(void)
{
	DWORD dwBytesRead = 0;
	DWORD dwBytesAvail = 0;

	BOOL peeked = PeekNamedPipe(hReadPipe, NULL, NULL, NULL,
		&dwBytesAvail, NULL);

	if(dwBytesAvail > 0)
	{
		char *buf = (char *)
			HeapAlloc(GetProcessHeap(), /*HEAP_ZERO_MEMORY*/ 0, dwBytesAvail + 1);

		if(buf)
		{
			ReadFile( hReadPipe, (LPVOID) buf, dwBytesAvail, &dwBytesRead, NULL );
			buf[dwBytesRead] = 0;
			Edit_ReplaceSel( hwndOutput, buf );
			BOOL freed = HeapFree(GetProcessHeap(), 0, buf);
		}
	}
}

// continuing with m3u example code:

int g_registered_command=0;
bool started = false;

// This is the function that gets called when the user hits our command key
// or chooses our menu item:

bool hookCommandProc(int command, int flag)
{
	if (g_registered_command && command == g_registered_command)
	{
		if( !hReaLuaWnd )	// exists and goes away again and again
		{
			CreateReaLuaWindow();
		}
		if(!started)		// only starts once
		{
			RedirectStdOut();		// sendcs() is preferred!
			fprintf(stdout, "%s  %s\r\n\r\n", LUA_RELEASE, LUA_COPYRIGHT);
//			fprintf(stdout, "%s\r\n\r\n", REALUA_VERSION);
			PrintStdOut();
			started = true;
		}

		ShowWindow(hReaLuaWnd, SW_SHOW);
		UpdateWindow(hReaLuaWnd);
		BringWindowToTop(hReaLuaWnd);

		return true;
	}
	return false;
}

extern "C"
{

// This is the function that gets called when Reaper starts up:

REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(REAPER_PLUGIN_HINSTANCE hInstance, reaper_plugin_info_t *rec)
{
  if (rec)
  {
    if (rec->caller_version != REAPER_PLUGIN_VERSION || !rec->GetFunc)
      return 0;

	gacreg.accel.cmd = g_registered_command = rec->Register("command_id","ReaLua");

    if (!g_registered_command) return 0; // failed getting a command id, fail!

    rec->Register("gaccel",&gacreg);
    rec->Register("hookcommand",hookCommandProc);

	initFunctionPointers(rec);
	startLua();

hInst = hInstance; // Store instance handle in our global variable
MyRegisterClass(hInstance);

    HMENU hMenu = GetSubMenu(GetMenu(GetMainHwnd()),6);
    MENUITEMINFO mi={sizeof(MENUITEMINFO),};
    mi.fMask = MIIM_TYPE | MIIM_ID;
    mi.fType = MFT_STRING;
    mi.dwTypeData = "Start Lua Shell";
    mi.wID = g_registered_command;
    InsertMenuItem(hMenu, 0, TRUE, &mi); 

	// our plugin registered, return success

    return 1;
  }
  else
  {
	stopLua();
//	FreeConsole();
	return 0;
  }
}

};	// extern "C"





/*
**

  REAPER example_m3u plug-in 
  Copyright (C) 2005-2008 Cockos Incorporated

  provides m3u as project import (not very useful but a good demonstration)


  This software is provided 'as-is', without any express or implied
  warranty.  In no event will the authors be held liable for any damages
  arising from the use of this software.

  Permission is granted to anyone to use this software for any purpose,
  including commercial applications, and to alter it and redistribute it
  freely, subject to the following restrictions:

  1. The origin of this software must not be misrepresented; you must not
     claim that you wrote the original software. If you use this software
     in a product, an acknowledgment in the product documentation would be
     appreciated but is not required.
  2. Altered source versions must be plainly marked as such, and must not be
     misrepresented as being the original software.
  3. This notice may not be removed or altered from any source distribution.


  */
